package com.splic.framework.jwt.filter;

import cn.hutool.extra.spring.SpringUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.splic.common.core.DeviceContext;
import com.splic.common.core.domain.ResponseObj;
import com.splic.common.core.redis.RedisCache;
import com.splic.common.exception.BusinessException;
import com.splic.common.utils.security.AESUtils;
import com.splic.framework.jwt.auth.JwtToken;
import com.splic.framework.shiro.service.SysDeviceService;
import com.splic.product.domain.User;
import com.splic.product.dto.DeviceDto;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
import org.apache.shiro.web.util.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * jwt 自定义拦截器
 *
 * @author ruoyi
 */
public class JwtFilter extends AccessControlFilter
{
    private static final Logger LOGGER = LoggerFactory.getLogger(JwtFilter.class);

    private static final String AUTHZ_HEADER = "token";

    private final ThreadLocal<String> MSG_HOLDER = new ThreadLocal<>();

    private SysDeviceService deviceService;

    private RedisCache getRedisCache() {
        return SpringUtil.getBean(RedisCache.class);
    }


    @Override
    public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception
    {
        return super.onPreHandle(request, response, mappedValue);
    }

    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception
    {
        return this.executeLogin(request, response);
    }


    /**
     * 不需要校验token的url
     */
    public static String [] anonJwtStr = new String[]{
            "/apiLogin",
            "/apiThirdLogin",
            "/api/user/saveFeedback",
            "/api/user/resetUserPwd",
            "/api/user/resetCheck",
            "/api/other/getExchangeRateList",
            "/api/config",
            "/api/getRecommendList",
            "/api/isUpdateApp",
            "/api/getOaidFile",
            "/api/transfer/callbackTransfer",
            "/api/transfer/callbackDeepgram",
            "/api/other/getAccuAddress",
    };


    /**
     * 不需要校验globalParams的url
     */
    public static String [] globalParamsStr = new String[]{
            "/api/transfer/callbackTransfer",
            "/api/transfer/callbackDeepgram",
    };

    /**
     * 有token就校验 没有token就不校验的url
     */
    public static String [] anonOrInnerJwtStr = new String[]{
            "/api/device/reportInfo",
    };

    /**
     * 执行登录方法（UserRealm判断，异常返回false）
     */
    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception
    {
        try {

            //校验当前地址是否在开放接口之内
            boolean global = checkGlobalUrl(request);
            if (!global) {
                //校验全局参数
                buildGlobalParams(request);
            }

            //校验当前地址是否在开放接口之内
            boolean flg = checkAnonUrl(request);
            if (!flg){
                //校验token是否正确
                checkJwtToken(request, response);
            }
            return true;
        }catch (AuthenticationException e) {
            if (e.getCause() instanceof TokenExpiredException)
            {
                MSG_HOLDER.set("token已过期,请重新登录");
            }
            else if (e.getCause() instanceof JWTVerificationException)
            {
                MSG_HOLDER.set("用户密码错误,请重新登录");
            }
            else
            {
                //MSG_HOLDER.set("用户信息验证失败：" + e.getMessage());
                MSG_HOLDER.set(e.getMessage());
            }
            return false;
        } catch (BusinessException e) {
            return false;
        }
    }

    private boolean checkAnonUrl(ServletRequest request) {
        return isContainUrl(request,anonJwtStr);
    }

    private boolean checkGlobalUrl(ServletRequest request) {
        return isContainUrl(request,globalParamsStr);
    }

    private boolean isContainUrl(ServletRequest request, String [] jwtStr) {
        String url =  ((ShiroHttpServletRequest) request).getRequestURI();
        //此处会出现重复/   //会导致下方equalsAny 判断失败
        url = url.replace("product","").replace("//","/");

        for(String anonUrl:jwtStr){
            if(url.contains(anonUrl)){
                return true;
            }
        }
        return false;
    }

    /**
     * 构建全局参数
     * @param request
     */
    private void buildGlobalParams(ServletRequest request) {
        String params = WebUtils.toHttp(request).getHeader("globalParams");
        if (StringUtils.isEmpty(params))
        {
            MSG_HOLDER.set("消息头全局参数不正确，header需要携带globalParams参数");
            throw new BusinessException("消息头全局参数不正确，header需要携带globalParams参数");
        }
        String encrypted = WebUtils.toHttp(request).getHeader("encrypted");
        if (!StringUtils.isEmpty(encrypted) && "true".equals(encrypted)) {
            try {
                params = AESUtils.decrypt(params, "pRvxtOnSZepDn1kTNXdxIw==");
            } catch (Exception e) {
                LOGGER.error("消息头全局参数不正确，globalParams参数解析失败{}", params, e);
                throw new BusinessException("消息头全局参数不正确，globalParams参数解析失败");
            }
        }
        DeviceDto deviceDto = JSONObject.parseObject(params,DeviceDto.class);
        if(deviceDto == null){
            MSG_HOLDER.set("消息头全局参数不正确，header需要携带global_params参数");
            throw new BusinessException("消息头全局参数不正确，header需要携带global_params参数");
        }
        String deviceIdentify = deviceService.interfaceNeedInfo(deviceDto);
        deviceDto.setDeviceIdentify(deviceIdentify);
        DeviceContext.putGlobalParams(JSONObject.parseObject(deviceDto.toString()));
    }

    /**
     * 校验token
     * @param request
     * @param response
     */
    private void checkJwtToken(ServletRequest request,ServletResponse response) {
        String token = WebUtils.toHttp(request).getHeader(AUTHZ_HEADER);
        if (StringUtils.isEmpty(token) )
        {
            //当url时可以不校验时 token为空就直接略过
            if(isContainUrl(request,anonOrInnerJwtStr)) {
                return;
            }
            MSG_HOLDER.set("消息头不正确，header需要携带token参数");
            throw new BusinessException("消息头不正确，header需要携带token参数");
        }
        JwtToken jwtToken = new JwtToken(token);
        Subject sub = this.getSubject(request, response);
        sub.login(jwtToken);
        User user = (User) sub.getPrincipal();

        RedisCache redisCache = getRedisCache();
        LinkedHashMap<String, String> userMap =  redisCache.getCacheObject(user.getUserIdentify() + "_" + DeviceContext.getProductPackageKey());
        if (userMap == null) {
            userMap = new LinkedHashMap<>();
        }
        List<String> oldToken = redisCache.getCacheObject(user.getUserIdentify() + "oldToken");
        if (oldToken == null) {
            oldToken = new ArrayList<>();
        }
        if (!oldToken.contains(token)) {
            oldToken.add(token);
            redisCache.setCacheObject(user.getUserIdentify() + "oldToken", oldToken);
            redisCache.setCacheObject(token,DeviceContext.getDeviceIdentify() + "_" + DeviceContext.getProductPackageKey(), 60, TimeUnit.DAYS);
            userMap.put(DeviceContext.getDeviceIdentify(), token);
        } else {
            try {
                if (!redisCache.hasKey(token)) {
                    MSG_HOLDER.set("登录过期,请重新登录");
                    throw new BusinessException("登录过期,请重新登录", 1000);
                } else {
                    if (!userMap.containsKey(DeviceContext.getDeviceIdentify())) {
                        MSG_HOLDER.set("登录过期,请重新登录");
                        throw new BusinessException("登录过期,请重新登录", 1000);
                    } else {
                        redisCache.setCacheObject(token,DeviceContext.getDeviceIdentify() + "_" + DeviceContext.getProductPackageKey(), 60, TimeUnit.DAYS);
                    }
                }
            } catch (Exception e) {
                MSG_HOLDER.set("登录过期,请重新登录");
                throw new BusinessException("登录过期,请重新登录", 1000);
            }
        }
    }


    /**
     * 请求前处理,处理跨域
     */
    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception
    {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
        httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
        httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));
        // 跨域时,option请求直接返回正常状态
        if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name()))
        {
            httpServletResponse.setStatus(HttpStatus.OK.value());
            return false;
        }
        return super.preHandle(request, response);
    }

    /**
     * 异常处理
     */
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception
    {
        this.jwtFail(request, response, 401, "对不起,您无权限进行操作!");
        return false;
    }

    /**
     * 认证失败，异常返回
     */
    protected void jwtFail(ServletRequest request, ServletResponse response, int code, String message)
    {
        HttpServletResponse httpResponse = WebUtils.toHttp(response);
        String contentType = "application/json;charset=UTF-8";
        httpResponse.setStatus(401);
        httpResponse.setContentType(contentType);
        try
        {
            String msg = StringUtils.isNotEmpty(MSG_HOLDER.get()) ? MSG_HOLDER.get() : message;
            PrintWriter printWriter = httpResponse.getWriter();
            printWriter.append(JSON.toJSONString(ResponseObj.ERROR(msg,code+"")));
        }
        catch (IOException e)
        {
            LOGGER.error("sendChallenge error,can not resolve httpServletResponse");
        }
    }

    public void setGlobalParamsDeviceService(SysDeviceService sysDeviceService)
    {
        this.deviceService = sysDeviceService;
    }
}
